home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 1.iso / ARGONET / PD / FILER / X-FILES.ZIP / 057 / X-Recover / c / x-recover
Text File  |  1997-01-13  |  70KB  |  2,404 lines

  1. /* x-recover.c 0·11
  2.  * Based (partly) on x-check.c by Andy Armstrong
  3.  * Needs chunks.h from the X-files distribution
  4.  *
  5.  * © Nicholas Clark 1996/1997
  6.  *
  7.  * Version history:
  8.  *
  9.  * 0·01 28/12/96
  10.  * First version. Successfully extracts all chunks from an (intact) X-File.
  11.  * Not really tested - did get all of the Perl 5.003 source (640 files)
  12.  * Checkmap still happy afterwards 8-)
  13.  *
  14.  * 0·02 30/12/96
  15.  * Added options to specify file offset of chunktable and root directory
  16.  * (Get this from x-guess)
  17.  * Implemented chunktable. Root directory may prove more problematic.
  18.  * (Currently do entire directory in one go by malloc()ing a buffer and reading
  19.  * the lot in. I guess I'm going to have to hunt the root directory in the chunk
  20.  * table to find out how big it is (xFiles_dirHeader.size is the *number* of
  21.  * entries, rather than the (floating) size of the names))
  22.  *
  23.  * 0·03 30/12/96
  24.  * Re-wrote dir code to read directories file by file. This allows x-recover to
  25.  * 1: Cope with directories that have an incorrect size in the chunkTable
  26.  *    (I have one of these I made accidentally during testing - I copied an
  27.  *     x-File after the chunktable was written, but before the cached directory
  28.  *     was written)
  29.  * 2: Read directories (eg the root directory) from an arbitrary disc location.
  30.  *
  31.  * 0·04 01/01/97
  32.  * Method 2 fully implemented
  33.  * Recovers all files that correlate between directories and chunktable
  34.  * Attempts to match chunkless files to chunks of the same size.
  35.  * Recovers these files.
  36.  * Recovers all so-far unrecovered chunks
  37.  * [Methods 1 and 2 still rely on an intact chunktable. If your chunktable is
  38.  * missing (and x-guess can't find it) or destroyed, you have a problem.
  39.  * It's just like trying to recover a disc with no discmap - you have to guess
  40.  * file type and size from the contents (assuming that there is some
  41.  * characteristic to recognise.
  42.  * Until someone writes a centralised file type database containing:
  43.  *   mime type
  44.  *   dos extension
  45.  *   unix suffix
  46.  *   mac type
  47.  *   identification by contents (/etc/magic in Unix)
  48.  *   [optionally icon]
  49.  * xrecover_guess-o-matic will have to wait.]
  50.  *
  51.  * 0·05 02/01/97
  52.  * Spellchecked the source code!
  53.  * eg snytax is now syntax
  54.  * Important lesson: Check you executables before distribution
  55.  * It did compile, but it didn't work. Fixed it
  56.  * Much now tidier if xrecover_recoverChunkChkdsk fails to read any bytes
  57.  * Tidied up "no dirhash for filename entry" check
  58.  *
  59.  * 0·06 02/01/97
  60.  * Merged x-guess as option -g
  61.  * Added déjà vu flag to stop infinite loops if the directory tree becomes
  62.  * tangled (precursor to method 3 - readDirsRaw which will have to reparent
  63.  * directories)
  64.  * Fixed -r to figure out the chunk the root dir occupies (if possible)
  65.  * (Usually 100 !) Wrote the documentation. We do documentation?
  66.  *
  67.  * 0·07 03/01/97
  68.  * Added -a and -f flags. (and implemented them!)
  69.  *
  70.  * 0·08 04/01/97
  71.  * Documentation up to date.
  72.  * Can now do options all run together - eg
  73.  * x-recover  -nvv1t 1024 $.Yaffel.dump.Perl.perl5003
  74.  * (or x-recover  -nvv1t1024 $.Yaffel.dump.Perl.perl5003)
  75.  * although one must have a space after the offset of -r or -t
  76.  * Fixed two bugs ( déjà vu flag and sprintf to a possible NULL pointer )
  77.  * See below for our policy on bugs.
  78.  *
  79.  * 0·09 07/01/97
  80.  * Fixed design - now get the number of chunks from chunktable chunk 0 rather
  81.  * than the header if a chunktable offset is specified
  82.  *
  83.  * 0·10 08/01/97
  84.  * The 'spec' may say that dirHashes are padded to the word boundary with zeros,
  85.  * but it doesn't say anything about the full leafname. Whoops.
  86.  * Method 3 remains to be implemented...
  87.  *
  88.  * 0·11 13/01/97
  89.  * What was that about bugs? printChunkDir() now realises that a directory in
  90.  * chunk 'zero' isn't actually in a known chunk.
  91.  * The syntax message kicks in if you forgot to supply a 'problem'
  92.  * (I was getting to clever with all the stuff to cope with -t1024 -t 1024 )
  93.  * Fixed the malloc for the chunkTable to use the size we are going to read
  94.  * (see version 0·09 comment) rather than the size in the header. If we read
  95.  * more than the header... ***boom***
  96.  * 
  97.  *
  98.  * Bugs: We don't do bugs.
  99.  *
  100.  * None known. Please report bugs (preferably with fixes) to <bagpuss@done.net>
  101.  * If you can supply an x-File to demonstrate then this would be useful.
  102.  * Currently I'm quite happy for relevant e-mail up to 100Kb, but if
  103.  * bagpuss.done.net is up then use anonymous ftp to upload problem files.
  104.  * (Files up to 100Mb acceptable by this method. No, I'm not confusing Kb with
  105.  * Mb. If you're on Janet then you should be able to shift it to me in 7
  106.  * minutes.)
  107.  */
  108.  
  109. #include <stdio.h>
  110. #include <stdlib.h>
  111. #include <string.h>
  112. #include <ctype.h>
  113. #include <stdarg.h>
  114. #include <limits.h>
  115. #include "chunks.h"
  116.  
  117. #ifdef __riscos
  118. #include "kernel.h"
  119. #include "SWIs.h"
  120. #endif
  121.  
  122. #ifdef __GNUC__
  123. #include "unistd.h"
  124. #endif
  125.  
  126. #define _max(x, y) ((x) > (y) ? (x) : (y))
  127. #define _min(x, y) ((x) < (y) ? (x) : (y))
  128. #define _four( N ) ((((N)-1)|3)+1)
  129.  
  130. #ifndef FREE
  131. #define FREE 0x45455246
  132. #endif
  133.  
  134. #ifdef __riscos
  135. static const char *xrecover_dirSep = ".";
  136. #else
  137. /* Should work Unix, Amiga, DOG (DOG internals don't care whether it's \ or /
  138.  * Not that I care about DOG. Well I do actually - I care that it is extant and
  139.  * still wasting people's time, and I work towards its extinction )
  140.  * "Dummies Guide to Memory Management" - Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaargh
  141.  * *Any* decent system just works. (usually because it comes with a flat memory
  142.  * model.)
  143.  */
  144. static const char *xrecover_dirSep = "/";
  145. #endif
  146.  
  147. enum {
  148.   xrecover_bufferSize = xFiles_ALLOCATIONUNIT * 64
  149. };
  150. /* If I do #define then I get text substitution everywhere (including functions)
  151.  * I believe that const int will allocate memory (so that the address can be
  152.  * taken)
  153.  */
  154.  
  155. typedef union {
  156.   xFiles_header        header;
  157.   xFiles_chunk        chunk;
  158.   xFiles_dirHeader    dirHeader;
  159.   char            padding[xFiles_ALLOCATIONUNIT];
  160. } xguess_block;
  161.  
  162.  
  163. typedef struct
  164. {
  165.   xFiles_dirEntry    dirEntry;
  166.   char            name[xFiles_MAXNAME+1];
  167.   /* Does xFiles_MAXNAME include the '\0' ? */
  168. } xrecover_dirEntryWithName;
  169.  
  170.  
  171. typedef enum {
  172.   xrecover_untouched    = 0x00,
  173.   xrecover_touched    = 0x01,
  174.   xrecover_foundChunk    = 0x02,
  175.   xrecover_sizeMatches    = 0x04,
  176.   xrecover_started    = 0x08,
  177.   /* An attempt to recover named file has been made. Stops list iterator
  178.    * attempting to reopen an illegal filename several times.
  179.    */
  180.   xrecover_readWhole    = 0x10,
  181.   xrecover_crossLinked    = 0x20,
  182.   xrecover_openFail    = 0x40,
  183.   xrecover_dejaVu    = 0x80,    /* Flag on entering a directory, to stop
  184.                  * infinite loops if directory tree has become
  185.                  * tangled */
  186.   xrecover_sorted    = 0x1F    /* Everything hunky-dory with this file */
  187. } xrecover_fileStatus;
  188.  
  189. typedef xrecover_fileStatus xrecover_chunkStatus;
  190.  
  191. typedef struct xrecover_filenameList_struct
  192. {
  193.   struct xrecover_filenameList_struct    *next;
  194.   xrecover_fileStatus            status;
  195.   unsigned                node;    /* only found in dirHash */
  196.   xFiles_dirEntry            dirEntry;
  197. } xrecover_filenameListEntry;
  198.  
  199. typedef struct
  200. {
  201.   xrecover_chunkStatus        status;
  202.   /* Currently only used for the déjà vu flag.
  203.    * If it's going to be used by anything else check that every fileList
  204.    * operation correctly updates it */
  205.   xrecover_filenameListEntry    *file;
  206.   unsigned long            references;
  207. /* Not entirely sure what xFiles_chunk::usage is for - maybe I should use that
  208.  * to record how many directory entries I find pointing to the chunk */
  209. } xrecover_chunkTableCrossCheck;
  210.  
  211.  
  212.  
  213. typedef union {
  214.   xFiles_dirHeader        dirHeader;
  215.   xFiles_dirEntry        dirEntry;
  216.   xrecover_dirEntryWithName    file;
  217. } xrecover_block;
  218.  
  219. typedef enum {
  220.   xrecover_qualityEvery1024,    /* Read in 1024 byte quanta. Check each quantum
  221.                  * for dir signature. If dir, skip through it
  222.                  * If not, append to previous file quanta */
  223.   xrecover_qualityChkdsk,    /* Read in chunktable. Write out all file
  224.                  * chunks */
  225.   xrecover_qualityReadDirs,    /* Read in chunktable. Read in dir structure.
  226.                  * Attempt to find each named file. Write out
  227.                  * all orphaned file chunks */
  228.   xrecover_qualityReadDirsRaw    /* As above, but run after collecting all known
  229.                  * dirs, scan x-file for other dir chunks. */
  230. } xrecover_qualityLevel;
  231.  
  232. /* This is for return values */
  233. typedef enum {
  234.   xrecover_chunkSuccess,
  235.   xrecover_chunkDejaVu,
  236.   xrecover_chunkCantOpen,
  237.   xrecover_chunkReadFail,
  238.   xrecover_chunkWriteFail,
  239.   xrecover_chunkAttrFail
  240. } xrecover_chunkResult;
  241.  
  242. typedef enum {
  243.   xrecover_inDirLoad,
  244.   xrecover_inDirExec,
  245.   xrecover_inDirSize,
  246.   xrecover_inDirAttr,
  247.   xrecover_inDirNameLen,
  248.   xrecover_inDirName,
  249.   xrecover_inDirUnknown
  250. } xrecover_inDirWhere;        /* The ill-fated 1024 byte block dir-streamer */
  251.  
  252. typedef struct {
  253.   BOOL valid;
  254.   unsigned long offset;
  255. } xrecover_Offset;
  256.  
  257.  
  258. /* Both these strings have the property that the printf()ed output is the same
  259.  * length as the string 8-)
  260.  * The program assumes this further on 8-(
  261.  *
  262.  * (BUG - will overflow for X-Files with > 9999 entries)
  263.  */
  264. static const char *chkdskString      = "file%04d";
  265. static const char *chkdskStringMS = "FILE%04d/CHK";    /* 8-) */
  266.  
  267. static const char *xrecover_FileType( signed long load, xFiles_attr attr )
  268. {
  269.   static char buffer[10];
  270.  
  271.   if( attr & xFiles_isDir )
  272.   {
  273.     strcpy( buffer, "Directory" );
  274.   }
  275.   else
  276.   {
  277.     strcpy( buffer, "         " );
  278.  
  279.     if( ( load >> 24 ) == -1 )
  280. #ifdef __riscos
  281.     {
  282.       _kernel_swi_regs regs;
  283.       _kernel_oserror *err;
  284.  
  285.       regs.r[0] = 18;
  286.       regs.r[2] = (int) ( load >> 8 ) & 0xFFF;
  287.  
  288.       if( err = _kernel_swi( OS_FSControl, ®s, ®s ), err )
  289.       {
  290.     fprintf( stderr, "%s\n", err->errmess );
  291.       }
  292.       else
  293.       {
  294.     *( (int *) buffer ) = regs.r[2];
  295.     *( 1 + ( (int *) buffer ) ) = regs.r[3];
  296.       }
  297.     }
  298. #else
  299.     {
  300.       sprintf( buffer, "&%03lX     ", ( load >> 8 ) & 0xFFF );
  301.     }
  302. #endif
  303.   }
  304.   return buffer;
  305. }
  306.  
  307. static _kernel_oserror *mkdir( const char *dirname )
  308. #ifdef __riscos
  309. {
  310.   _kernel_swi_regs regs;
  311.  
  312.   regs.r[0] = 8;
  313.   regs.r[1] = (int) dirname;
  314.   regs.r[4] = xFiles_GROWDIRBY;    /* Why not? */
  315.  
  316.   return _kernel_swi( OS_File, ®s, ®s );
  317. }
  318. #else
  319. #error Wot no mkdir function?
  320. Should be fun on Unix [hint system( mkdir ) ]
  321. #endif
  322. /* Will have to re-do this subroutine for other OSes */
  323.  
  324. static const char *xrecover_Attr( xFiles_attr attr )
  325. {
  326.   static char buffer[8];
  327.   char *pos = buffer;
  328.  
  329.   if( attr & xFiles_isDir ) *pos++ = 'D';
  330.   if( attr & xFiles_locked ) *pos++ = 'L';
  331.   if( attr & xFiles_meRead ) *pos++ = 'R';
  332.   if( attr & xFiles_meWrite ) *pos++ = 'W';
  333.  
  334.   *pos++ = '/';
  335.  
  336.   if( attr & xFiles_youRead ) *pos++ = 'r';
  337.   if( attr & xFiles_youWrite ) *pos++ = 'w';
  338.  
  339.   *pos = '\0';
  340.  
  341.   return buffer;
  342. }
  343.  
  344. static BOOL xrecover_ReadOffset( xrecover_Offset *offset, const char *value )
  345. {
  346.   char *end;
  347.  
  348.   offset->valid = FALSE;
  349.   if( 0 == value ) return FALSE;
  350.  
  351.   offset->offset = strtoul( value, &end, 10 );
  352.  
  353.   if( end == value )
  354.   {
  355.     fprintf( stderr, "Failed to make sense of offset '%s'\n", value );
  356.     return FALSE;
  357.   }
  358.  
  359.   offset->valid = TRUE;
  360.   return TRUE;
  361. }
  362.  
  363. /* 2 functions from x-check */
  364. static int power2( int x )
  365. {
  366.   while( x && !( x & 1 ) ) x >>= 1;
  367.   return x == 1;
  368. }
  369.  
  370. static void test( int c, const char *fmt, ... )
  371. {
  372.   va_list ap;
  373.   va_start( ap, fmt );
  374.  
  375.   if( !c )
  376.   {
  377.     vfprintf( stderr, fmt, ap );
  378.   }
  379.  
  380.    va_end( ap );
  381. }
  382.  
  383. static unsigned xrecover_chunkFromOffset( const xFiles_chunk *chunkTable,
  384.                       unsigned maxChunk,
  385.                       const unsigned long offset )
  386. {
  387.   while( maxChunk-- )
  388.   {
  389.     if( chunkTable[maxChunk].usage != FREE
  390.     && chunkTable[maxChunk].offset == offset ) return maxChunk;
  391.   }
  392.   return 0;
  393. }
  394.  
  395.  
  396. static void printChunkDir( FILE *where, const int chunkNum,
  397.                const char *pathToHere )
  398. {
  399.   if( chunkNum )
  400.   {
  401.     fprintf( where, "Chunk %d ", chunkNum );
  402.   }
  403.   else
  404.   {
  405.     /* Chunk 0 is the chunkTable. Therefore it can't be a directory. So this
  406.        must be a directory referenced by file offset, and we don't know it's
  407.        chunk */
  408.     fputs( "Chunk ? ", where );
  409.   }
  410.   
  411.   if( pathToHere )
  412.   {
  413.     fprintf( where, ( *pathToHere ) ? "[directory '%s'] "
  414.                     : "[root directory] ", pathToHere );
  415.   }
  416. }
  417.  
  418. static void printChunkDirEntry( FILE *where, const int chunkNum,
  419.                 const char *pathToHere, const unsigned entry,
  420.                 const char *filename, const unsigned node )
  421. {
  422.   printChunkDir( where, chunkNum, pathToHere );
  423.   fprintf( where, "entry %d ", entry );
  424.   if( filename )
  425.   {
  426.     fprintf( where, "('%s') ", filename );
  427.   }
  428.   if( node )
  429.   {
  430.     fprintf( stderr, "->[chunk %d] ", node );
  431.   }
  432. }
  433.  
  434. static unsigned roundDown( xFiles_header *header, unsigned pos )
  435. {
  436.   return pos - pos % header->allocationUnit;
  437. }
  438.  
  439. static unsigned roundUp( xFiles_header *header, unsigned pos )
  440. {
  441.   return pos - ( pos % header->allocationUnit ) + header->allocationUnit;
  442. }
  443.  
  444. static int okOffset( xFiles_header *header, unsigned offset )
  445. {
  446.    return ( offset % header->allocationUnit ) == 0;
  447. }
  448.  
  449. static int hashCmp( const void *alpha, const void *omega )
  450. {
  451.   const xFiles_dirHash *zero = alpha;
  452.   const xFiles_dirHash *inf = omega;
  453.  
  454.   return (int) zero->entryPos - (int) inf->entryPos;
  455. }
  456.  
  457. static int filenameListEntryCmp( const void *e, const void *to_the )
  458. {
  459.   xrecover_filenameListEntry *const *pi = e;
  460.   xrecover_filenameListEntry *const *i = to_the;
  461.  
  462.   return (int) (*pi)->dirEntry.size - (int) (*i)->dirEntry.size;
  463. }
  464.  
  465. static int chunkSizeCmp( const void *plus, const void *one )
  466. {
  467.   const xFiles_chunk *const *equals = plus;
  468.   const xFiles_chunk *const *zero = one;
  469.  
  470.   /* Currently this function is only used on known non-free chunks */
  471.   return (int) (*equals)->size - (int) (*zero)->size;
  472. }
  473.  
  474. static int chunkOffsetCmp( const void *alpha, const void *omega )
  475. {
  476.   /* I think it was the OED which had aardvark and zygote as its first and last
  477.    * real words.
  478.    *
  479.    * (Unfortunately, all geologists know that aa is blocky lava) */
  480.   const xFiles_chunk *const *aardvark = alpha;
  481.   const xFiles_chunk *const *zygote = omega;
  482.  
  483.   /* Get all free chunks at one end */
  484.   if( (*aardvark)-> usage == FREE ) return -1;
  485.   return (*zygote)-> usage == FREE ? +1
  486.      : (int) (*aardvark)->offset - (int) (*zygote)->offset;
  487. }
  488.  
  489. static int xrecover_xguess( const char *suspect )
  490. {
  491.   xguess_block block;
  492.   FILE *in;
  493.   unsigned whereIsRootChunk;
  494.   long where;
  495.  
  496.   if( sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT )
  497.   {
  498.     fprintf( stderr,
  499.          "Awooga, awooga: sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT"
  500.          "\n\t\t%d != %d\n",
  501.          sizeof( xguess_block ), xFiles_ALLOCATIONUNIT );
  502.     return 255;
  503.   }
  504.  
  505.   if( 0 == ( in = fopen( suspect, "rb" ) ) )
  506.   {
  507.     fprintf( stderr, "Could not open file '%s'\n", suspect );
  508.   }
  509.   else if( 0 == fread( &block, sizeof( block ), 1, in ) )
  510.   {
  511.     fprintf( stderr, "Could not read header from file '%s'\n", suspect );
  512.   }
  513.   else
  514.   {
  515.     test( block.header.sig == xFiles_SIG,
  516.       "Missing xFiles_SIG - read %8X, should be %8X\n",
  517.       block.header.sig, xFiles_SIG );
  518.     test( block.header.hdrSize == sizeof( xFiles_header ),
  519.       "Illegal hdrSize %d\n",
  520.       block.header.hdrSize );
  521.  
  522.     test( block.header.structureVersion == xFiles_STRUCTUREVERSION,
  523.       "Illegal structureVersion %d\n",
  524.       block.header.structureVersion );
  525.  
  526.     test( block.header.directoryVersion == xFiles_DIRECTORYVERSION,
  527.       "Illegal directoryVersion %d\n",
  528.       block.header.directoryVersion );
  529.  
  530.     test( power2( block.header.allocationUnit ),
  531.       "Illegal allocationUnit %d\n",
  532.       block.header.allocationUnit );
  533.  
  534.     printf( "File '%s' header states:\n"
  535.         "         Chunktable at %d\n",
  536.         suspect, block.header.chunkTable.offset );
  537.  
  538.     whereIsRootChunk =     block.header.chunkTable.offset
  539.                + block.header.rootChunk * sizeof( xFiles_chunk );
  540.  
  541.     if( fseek( in, whereIsRootChunk, SEEK_SET ) )
  542.     {
  543.       fprintf( stderr, "Could not seek to root chunk at %d\n",
  544.            whereIsRootChunk );
  545.     }
  546.     else if( 0 == fread( &block, sizeof( xFiles_chunk ), 1, in ) )
  547.     {
  548.       fprintf( stderr, "Could not read root directory chunk at %d\n",
  549.            whereIsRootChunk );
  550.     }
  551.     else
  552.     {
  553.       printf( "Root directory is in chunk %d, file offset %d\n",
  554.           block.header.rootChunk, block.chunk.offset );
  555.     }
  556.   }
  557.  
  558.   rewind( in );
  559.  
  560.   while( ( where = ftell( in ) ),
  561.      ( 0 != fread( &block, sizeof( block ), 1, in ) ) )
  562.   {
  563.     if(       ( block.chunk.offset == where )
  564.     && ( block.chunk.size % sizeof( xFiles_chunk ) == 0 ) )
  565.     {
  566.       /* Smells like the chunktable if the first 'chunk' points to this
  567.        * position in the file (which is read into where before the chunktable)
  568.        * and the size of the 'chunktable' is a multiple of the size of a chunk
  569.        */
  570.        printf( "Could be chunktable at %ld\n", where );
  571.     }
  572.     else if(    ( block.dirHeader.sig == xFiles_DIRSIG )
  573.          && ( block.dirHeader.parent == 0 ) )
  574.     {
  575.       /* Smells like the root dir if there is a dir signature ('ANDY') and
  576.        * the parent dir is 0
  577.        * [So what's wrong with using 'Nick' as a dir signature 8-) ]
  578.        */
  579.        printf( "Could be root directory at                  %ld\n", where );
  580.     }
  581.   }
  582.  
  583.   return 0;
  584. }
  585.  
  586. static void xrecover_clearList( const unsigned chunkNum,
  587.                 xrecover_chunkTableCrossCheck *crossCheckEntry,
  588.                 xrecover_filenameListEntry *fileTree,
  589.                 int verbose )
  590. {
  591.   if( verbose > 0 )
  592.   {
  593.     printf( "Unlinking: chunk %d currently has %ld reference(s)\n", chunkNum,
  594.         crossCheckEntry->references );
  595.   }
  596.   while( fileTree )
  597.   {
  598.     if( fileTree->node == chunkNum )
  599.     {
  600.       fprintf( stderr, "Unlinking '%s' from chunk %d\n",
  601.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ),
  602.            chunkNum );
  603.       if( ( fileTree->status & ~xrecover_crossLinked ) == 0 )
  604.       {
  605.     fputs( "Wasn't cross linked", stderr );
  606.       }
  607.       /* Clear these flags */
  608.       fileTree->status &= ~( xrecover_foundChunk | xrecover_sizeMatches
  609.                  | xrecover_crossLinked );
  610.  
  611.       if( crossCheckEntry->references-- == 0 )
  612.       {
  613.     fprintf( stderr, "Chunk %d reference count incorrect\n", chunkNum );
  614.     crossCheckEntry->references = 0;
  615.       }
  616.     }
  617.  
  618.     fileTree = fileTree->next;
  619.   }
  620.   if( verbose > 0 )
  621.   {
  622.     printf( "                          now has %ld reference(s)\n",
  623.         crossCheckEntry->references );
  624.   }
  625.  
  626.   crossCheckEntry->status &= ~xrecover_crossLinked;
  627.   /* As it's clear it can't be cross linked */
  628. }
  629.  
  630. static void xrecover_printList( FILE *where,
  631.                 const xFiles_chunk *chunkTable,
  632.                 const xrecover_chunkTableCrossCheck
  633.                   *crossCheckTable,
  634.                 const xrecover_filenameListEntry *fileTree,
  635.                 int verbose )
  636. {
  637.   while( fileTree )
  638.   {
  639.     if( verbose == 0 )
  640.     {
  641.       fprintf( where, "%s\n",
  642.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
  643.     }
  644.     else
  645.     {
  646.       fprintf( where, "%08X %08X %8d %-7s %s %s\n", fileTree->dirEntry.load,
  647.            fileTree->dirEntry.exec, fileTree->dirEntry.size,
  648.            xrecover_Attr( fileTree->dirEntry.attr ),
  649.            xrecover_FileType( fileTree->dirEntry.load,
  650.                   fileTree->dirEntry.attr ),
  651.            ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
  652.       if( verbose > 1 )
  653.       {
  654.     if( fileTree->status & xrecover_foundChunk )
  655.     {
  656.       fprintf( where, "  chunk %d ", fileTree->node );
  657.       fprintf( where,
  658.            ( chunkTable[fileTree->node].size
  659.               == fileTree->dirEntry.size )
  660.            ? "size=%d" : "chunk.size=%d, dirEntry.size=%d",
  661.            chunkTable[fileTree->node].size, fileTree->dirEntry.size );
  662.       if( ( chunkTable[fileTree->node].size == fileTree->dirEntry.size )
  663.         == !!( fileTree->status & xrecover_sizeMatches ) )
  664.       {
  665.         fputc( '\n', where );
  666.       }
  667.       else
  668.       {
  669.         fputs( " [incorrectly flagged]\n", where );
  670.       }
  671.       if( fileTree->status & xrecover_crossLinked )
  672.       {
  673.         if( 1 == crossCheckTable[fileTree->node].references )
  674.         {
  675.           fputs( "  error - flagged as cross linked, but chunk has exactly "
  676.              "one reference\n", where );
  677.         }
  678.         else
  679.         {
  680.           fprintf( where, "  cross linked: total %ld cross references\n",
  681.                crossCheckTable[fileTree->node].references );
  682.         }
  683.       }
  684.       else
  685.       {
  686.         if( 1 != crossCheckTable[fileTree->node].references )
  687.         {
  688.           fprintf( where, "  error - not flagged as cross linked, chunk has"
  689.                " %ld cross references\n",
  690.                crossCheckTable[fileTree->node].references );
  691.         }
  692.       }
  693.     }
  694.     else
  695.     {
  696.       fputs( "  [no chunk found]\n", where );
  697.     }
  698.       }
  699.     }
  700.     fileTree = fileTree->next;
  701.   }
  702. }
  703.  
  704. static void
  705. xrecover_getProblemList( xrecover_filenameListEntry *fileTree,
  706.              xrecover_filenameListEntry **problems,
  707.              unsigned numProblems )
  708. {
  709.   xrecover_filenameListEntry **end = problems + numProblems;
  710.   while( fileTree )
  711.   {
  712.     if( ( fileTree->status & xrecover_foundChunk ) == 0 )
  713.     {
  714.       *problems = fileTree;
  715.       if( ++problems > end )
  716.       {
  717.     fputs( "Miscounted problems in the fileTree\n", stderr );
  718.     return;
  719.       }
  720.     }
  721.     fileTree = fileTree->next;
  722.   }
  723. }
  724.  
  725. static void xrecover_freeList( xrecover_filenameListEntry **fileTree )
  726. {
  727.   xrecover_filenameListEntry *temp;
  728.   while( *fileTree )
  729.   {
  730.     temp = (*fileTree)->next;
  731.     free( *fileTree );
  732.     *fileTree = temp;
  733.   }
  734. }
  735.  
  736. static xrecover_chunkResult
  737. xrecover_recoverDir( FILE *in, FILE *out, const char *victim,
  738.              const char *pathToHere,
  739.              const unsigned long start,
  740.              const unsigned chunkNum,
  741.              const xFiles_chunk *thisChunk,
  742.              const xFiles_chunk *chunkTable,
  743.              xrecover_chunkTableCrossCheck *crossCheckTable,
  744.              xrecover_filenameListEntry **fileTree,
  745.              const unsigned maxChunk, int verbose )
  746. {
  747.   /* out != NULL for text output
  748.    * pathToHere != NULL for full recursion
  749.    * if dirHeader == 0 need to read it from file
  750.    * start points to file offset of start of directory (good for seeking)
  751.    */
  752.   xrecover_block buffer;
  753.   xFiles_dirHeader header;
  754.   unsigned dirEntry = 0;
  755.   size_t maxSize;
  756.   unsigned long where = -1L;    /* ANSI's error result from ftell() */
  757.   xFiles_dirHash *dirHash, *currentDirHash, *dirHashLimit;
  758.   unsigned bytesRead, bytesToRead;
  759.   BOOL haveHash = FALSE;
  760.   const size_t pathLen = pathToHere ? ( 1 + strlen( pathToHere )
  761.                     + strlen( xrecover_dirSep ) ) : 0;
  762.   /* One for the '\0' */
  763.  
  764.  
  765.   if( thisChunk == 0 && chunkTable && chunkNum )
  766.   {
  767.     /* chunk 0 is always the chunkTable, so it can never be a directory */
  768.     thisChunk = chunkTable + chunkNum;
  769.   }
  770.  
  771.   if( chunkNum && crossCheckTable &&
  772.       ( crossCheckTable[chunkNum].status & xrecover_dejaVu ) )
  773.   {
  774.     printChunkDir( stderr, chunkNum, pathToHere );
  775.     fputs( "Déjà vu - been to this directory before", stderr );
  776.     if( crossCheckTable[chunkNum].file )
  777.     {
  778.       fprintf( stderr, " as '%s'\n",
  779.            ( (char *) &(crossCheckTable[chunkNum].file->dirEntry) )
  780.          + sizeof( xFiles_dirEntry ) );
  781.     }
  782.     else putc( '\n', stderr );
  783.  
  784.     if( out ) fclose( out );
  785.     return xrecover_chunkDejaVu;
  786.   }
  787.  
  788.   rewind( in );
  789.   if( fseek( in, start, SEEK_SET ) )
  790.   {
  791.     printChunkDir( stderr, chunkNum, pathToHere );
  792.     fprintf( stderr, "could not seek to start (offset %ld)\n", start );
  793.     if( out ) fclose( out );
  794.     return xrecover_chunkReadFail;
  795.   }
  796.  
  797.   if( 0 == fread( &header, sizeof( xFiles_dirHeader ), 1, in ) )
  798.   {
  799.     printChunkDir( stderr, chunkNum, pathToHere );
  800.     fputs( "could not read dirHeader\n", stderr );
  801.     if( out ) fclose( out );
  802.     return xrecover_chunkReadFail;
  803.   }
  804.  
  805.   if( *(int *) &header != xFiles_DIRSIG )
  806.   {
  807.     printChunkDir( stderr, chunkNum, pathToHere );
  808.     fprintf( stderr, "Missing xFiles_DIRSIG - read %8X, should be %8X\n",
  809.          *(int *) &header, xFiles_DIRSIG );
  810.   }
  811.  
  812.   if( verbose > 1 )
  813.   {
  814.     fputs( "Directory", stdout );
  815.     /* This has to be fputs() as puts() appends '\n'
  816.      * Why are puts, fputs and *printf so inconsistent? */
  817.     if( pathToHere )
  818.     {
  819.       printf( " '%s'", *pathToHere ? pathToHere : "<Root>" );
  820.     }
  821.     printf( ": size=%u used=%u\n", header.size, header.used );
  822.   }
  823.   
  824.   /* Whoops - they are unsigned. Can't compare. Changes %d to %u to remind me */
  825.   /*if( header.used < 0 || header.size < 0 )
  826.   {
  827.     printChunkDir( stderr, chunkNum, pathToHere );
  828.     fprintf( stderr, "size of directory (%d) and used entries (%d) should both "
  829.              "be positive\n", header.size, header.used );
  830.   }
  831.   else*/ if( header.used > header.size )
  832.   {
  833.     printChunkDir( stderr, chunkNum, pathToHere );
  834.     fprintf( stderr, "size of directory (%u) < used entries (%u)\n",
  835.              header.size, header.used );
  836.   }
  837.  
  838.   if( 0 == ( dirHash = malloc( sizeof( xFiles_dirHash ) * header.used ) ) )
  839.   {
  840.     printChunkDir( stderr, chunkNum, pathToHere );
  841.     fprintf( stderr, "failed to get %u bytes of memory for dir hash\n",
  842.          sizeof( xFiles_dirHash ) * header.used );
  843.     if( out ) fclose( out );
  844.     return xrecover_chunkReadFail;
  845.   }
  846.  
  847.   bytesRead = fread( dirHash, sizeof( xFiles_dirHash ), header.used, in );
  848.   if( bytesRead != header.used )
  849.   {
  850.     printChunkDir( stderr, chunkNum, pathToHere );
  851.     fprintf( stderr, "could only read %d of %d dirHash(es)\n",
  852.          bytesRead, header.used );
  853.     if( out ) fclose( out );
  854.     free( dirHash );
  855.     return xrecover_chunkReadFail;
  856.   }
  857.   /* Sort into position order. As far as I can tell X-Files 0·56 keeps its
  858.    * hash sorted by position order, but this doesn't seem to be part of the
  859.    * specification, so I won't assume it */
  860.  
  861.   qsort( dirHash, header.used, sizeof( xFiles_dirHash ), hashCmp );
  862.  
  863.   currentDirHash = dirHash;
  864.   dirHashLimit = dirHash + header.used;
  865.  
  866.   if( fseek( in, sizeof( xFiles_dirHash ) * ( header.size - header.used ),
  867.          SEEK_CUR ) )
  868.   {
  869.     printChunkDir( stderr, chunkNum, pathToHere );
  870.     fputs( "could not seek to start of dir entries\n", stderr );
  871.     if( out ) fclose( out );
  872.     free( dirHash );    /* Something says that putting goto exit; would
  873.              * reduce the possibility of bugs (in this case a
  874.              * memory leak on dirHash and an open file on out ) */
  875.     return xrecover_chunkReadFail;
  876.   }
  877.  
  878.   for( ; dirEntry < header.used; dirEntry++ )
  879.   {
  880.     where = ftell( in );
  881.     if( haveHash ) currentDirHash++;
  882.     /* Used currentDirHash, so advance it one */
  883.     if( 0 == fread( &buffer, sizeof( xFiles_dirEntry ), 1, in ) )
  884.     {
  885.       printChunkDir( stderr, chunkNum, pathToHere );
  886.       fprintf( stderr, "could not read directory entry %d\n",
  887.            dirEntry );
  888.     }
  889.     else
  890.     {
  891.       if( buffer.dirEntry.nameLen > xFiles_MAXNAME )
  892.       {
  893.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
  894.     fprintf( stderr, "reports nameLen as %d - truncating to %d\n",
  895.          buffer.dirEntry.nameLen, xFiles_MAXNAME );
  896.     buffer.dirEntry.nameLen = xFiles_MAXNAME;
  897.       }
  898.  
  899.       bytesToRead = _four( buffer.dirEntry.nameLen + 1 );
  900.       bytesRead = fread( &(buffer.file.name), 1, bytesToRead, in );
  901.       if( bytesRead < bytesToRead )
  902.       {
  903.     /* Terminate what we got */
  904.     buffer.file.name[bytesRead] = '\0';
  905.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  906.                 buffer.file.name, 0 );
  907.     fprintf( stderr, "could not read %u bytes of filename (got %u)\n",
  908.          bytesToRead, bytesRead );
  909.       }
  910.  
  911.       while( ( currentDirHash < dirHashLimit )
  912.          && ( currentDirHash->entryPos < ( where - start ) ) )
  913.       {
  914.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
  915.     fprintf( stderr, "no filename entry for dirhash:\n"
  916.          "\t\t'%c%c%c%c' chunk %d entryPos %d\n",
  917.          currentDirHash->nameStart[0], currentDirHash->nameStart[1],
  918.          currentDirHash->nameStart[2], currentDirHash->nameStart[3],
  919.          currentDirHash->node, currentDirHash->entryPos );
  920.     currentDirHash++;
  921.       }
  922.  
  923.       if( ( currentDirHash >= dirHashLimit )
  924.       || ( currentDirHash->entryPos != ( where - start ) ) )
  925.       {
  926.     haveHash = FALSE;
  927.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  928.                 buffer.file.name, 0 );
  929.     fputs( "no dirhash for filename entry\n", stderr );
  930.       }
  931.       else
  932.       {
  933.     haveHash = TRUE;
  934.     /* Check hash == name */
  935.     if( currentDirHash->nameStart[0] )
  936.     {
  937.       if( !(    currentDirHash->nameStart[0] == buffer.file.name[0]
  938.              && currentDirHash->nameStart[1] == buffer.file.name[1]
  939.              && ( currentDirHash->nameStart[1] == 0
  940.                   || ( currentDirHash->nameStart[2] == buffer.file.name[2]
  941.              && ( currentDirHash->nameStart[2] == 0
  942.                   || ( currentDirHash->nameStart[3] == buffer.file.name[3]
  943.                       ) ) ) ) ) )
  944.       {
  945.         /* Would it be faster to use *int compares if len > 3 and strcmp()
  946.          * where len < 4 ? */
  947.         printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  948.                     buffer.file.name, 0 );
  949.         fprintf( stderr, "does not match dirhash '%c%c%c%c'\n",
  950.              currentDirHash->nameStart[0],
  951.              currentDirHash->nameStart[1],
  952.              currentDirHash->nameStart[2],
  953.              currentDirHash->nameStart[3] );
  954.       }
  955.       /* else it matches */
  956.     }
  957.     else
  958.     {
  959.     printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  960.                 buffer.file.name, 0 );
  961.     fprintf( stderr, "has illegal empty dirhash '%c%c%c%c'\n",
  962.          currentDirHash->nameStart[0], currentDirHash->nameStart[1],
  963.          currentDirHash->nameStart[2], currentDirHash->nameStart[3] );
  964.         }
  965.       }
  966.  
  967.       where += sizeof( xFiles_dirEntry ) + bytesToRead;
  968.       /* Where we *should* be */
  969.       if( out )
  970.       {
  971.     fprintf( out, "%08X %08X %8d %-7s %s %s\n", buffer.dirEntry.load,
  972.            buffer.dirEntry.exec, buffer.dirEntry.size,
  973.            xrecover_Attr( buffer.dirEntry.attr ),
  974.            xrecover_FileType( buffer.dirEntry.load,
  975.                       buffer.dirEntry.attr ),
  976.            buffer.file.name );
  977.       }
  978.  
  979.       if( fileTree && pathToHere )
  980.       {
  981.     xrecover_filenameListEntry *current =
  982.       malloc( sizeof( xrecover_filenameListEntry )
  983.               + buffer.dirEntry.nameLen + pathLen );
  984.     char *entryPath = current ? ( (char *) &(current->dirEntry) )
  985.                     + sizeof( xFiles_dirEntry )
  986.                   : 0;
  987.     /* Relative path from x-file root to this entry */
  988.  
  989.     if( 0 == current ) continue;    /* Implicit goto the next loop */
  990.  
  991.     current->dirEntry = buffer.dirEntry;
  992.     if( *pathToHere )
  993.     {
  994.       /* Make a path */
  995.       sprintf( entryPath, "%s%s%s", pathToHere, xrecover_dirSep,
  996.            buffer.file.name );
  997.     }
  998.     else
  999.     {
  1000.       strcpy( entryPath, buffer.file.name );
  1001.     }
  1002.     current->status = xrecover_touched;
  1003.     current->node = haveHash ? currentDirHash->node : 0;
  1004.  
  1005.     if( current->node )
  1006.     {
  1007.       if( current->node >= maxChunk )
  1008.       {
  1009.         printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1010.                 buffer.file.name, 0 );
  1011.         fprintf( stderr, "chunk %d is out of range (%d)\n", current->node,
  1012.              maxChunk );
  1013.         current->node = 0;
  1014.       }
  1015.       else
  1016.       {
  1017.         if( chunkTable[current->node].usage == FREE )
  1018.         {
  1019.            printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1020.                    buffer.file.name, current->node );
  1021.            fputs( "is marked free\n", stderr );
  1022.            current->node = 0;
  1023.         }
  1024.         else
  1025.         {
  1026.           current->status |= xrecover_foundChunk;
  1027.           /* So we have a chunk... */
  1028.           if( crossCheckTable[current->node].file )
  1029.           {
  1030.         xrecover_filenameListEntry *crossLink =
  1031.           (crossCheckTable[current->node].file);
  1032.         printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1033.                     buffer.file.name, current->node );
  1034.         fprintf( stderr, "is cross linked with file '%s'\n",
  1035.              ( (char *) &(crossLink->dirEntry) )
  1036.              + sizeof( xFiles_dirEntry ) );
  1037.         /* Eeek. But wait for it */
  1038.  
  1039.         if( crossLink->status & xrecover_sizeMatches )
  1040.         {
  1041.           /* They think that they have their chunk */
  1042.           if( chunkTable[current->node].size == buffer.dirEntry.size )
  1043.           {
  1044.             /* And so do we */
  1045.             crossLink->status |= xrecover_crossLinked;
  1046.             current->status |= xrecover_crossLinked
  1047.                        | xrecover_sizeMatches;
  1048.             crossCheckTable[current->node].references += 1;
  1049.           }
  1050.           else
  1051.           {
  1052.             /* They match and we don't */
  1053.             current->status &= ~xrecover_foundChunk;
  1054.             fprintf( stderr, "File '%s' size %d tallies with "
  1055.                  "chunktable - unlinking '%s'\n",
  1056.                  ( (char *) &(crossLink->dirEntry) )
  1057.                  + sizeof( xFiles_dirEntry ),
  1058.                  chunkTable[current->node].size, buffer.file.name );
  1059.             current->node = 0;
  1060.           }
  1061.         }
  1062.         else
  1063.         {
  1064.           /* Their size doesn't match */
  1065.           if( chunkTable[current->node].size == buffer.dirEntry.size )
  1066.           {
  1067.             /* But ours does */
  1068.             current->status |= xrecover_sizeMatches;
  1069.             crossCheckTable[current->node].file = current;
  1070.             xrecover_clearList( current->node,
  1071.                     crossCheckTable + current->node,
  1072.                     *fileTree, verbose );
  1073.  
  1074.             if( crossCheckTable[current->node].references )
  1075.             {
  1076.               fprintf( stderr, "Awooga - failed to clear the list - %ld"
  1077.                    " references remain",
  1078.                    crossCheckTable[current->node].references );
  1079.               current->status |= xrecover_crossLinked;
  1080.             }
  1081.             else
  1082.             {
  1083.               crossCheckTable[current->node].references = 1;
  1084.             }
  1085.           }
  1086.           else
  1087.           {
  1088.             /* Our size doesn't match either */
  1089.             crossLink->status |= xrecover_crossLinked;
  1090.             current->status |= xrecover_crossLinked;
  1091.             crossCheckTable[current->node].references += 1;
  1092.           }
  1093.         }
  1094.  
  1095.           }
  1096.           else
  1097.           {
  1098.         /* We have the chunk all to ourselves */
  1099.         if( chunkTable[current->node].size == buffer.dirEntry.size )
  1100.         {
  1101.           /* And the size matched 8-) */
  1102.           current->status |= xrecover_sizeMatches;
  1103.         }
  1104.         crossCheckTable[current->node].file = current;
  1105.         crossCheckTable[current->node].references = 1;
  1106.           }
  1107.  
  1108.           if( buffer.dirEntry.attr & xFiles_isDir )
  1109.           {
  1110.         size_t size = victim ? strlen( victim )
  1111.                        + strlen( xrecover_dirSep )
  1112.                        + strlen( entryPath ) + 1
  1113.                      : 0;
  1114.         char *tempBuffer = size ? malloc( size ) : 0;
  1115.         _kernel_oserror *err = 0;
  1116.  
  1117.         if( size )
  1118.         {
  1119.           if( tempBuffer == 0 )
  1120.           {
  1121.             printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1122.                     buffer.file.name, current->node );
  1123.             fprintf( stderr, "failed to get %u bytes of memory for dir "
  1124.                  "name.\nCannot create output sub-directory, so "
  1125.                  "will not recurse into input sub-directory\n",
  1126.                  size );
  1127.             err = (_kernel_oserror *) -5;
  1128.             /* Should address exception/abort on data transfer if anyone
  1129.              * is silly enough to try to read errmess
  1130.              */
  1131.             current->status |= xrecover_started;
  1132.             /* Flag to stop list routine stamping all over any offending
  1133.              * file that got in our way */
  1134.           }
  1135.           else
  1136.           {
  1137.             sprintf( tempBuffer, "%s%s%s", victim, xrecover_dirSep,
  1138.                  entryPath );
  1139.           }
  1140.         }
  1141.         if( size && ( 0 != ( err = mkdir( tempBuffer ) ) ) )
  1142.         {
  1143.           printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1144.                       buffer.file.name, current->node );
  1145.           fprintf( stderr, "%s\n", err->errmess );
  1146.         }
  1147.         free( tempBuffer );
  1148.  
  1149.         if( 0 == err )
  1150.         {
  1151.           xrecover_recoverDir( in, out, victim,
  1152.                        entryPath,
  1153.                        chunkTable[current->node].offset,
  1154.                        current->node, 0, chunkTable,
  1155.                        crossCheckTable, fileTree,
  1156.                        maxChunk, verbose );
  1157.           rewind( in );    /* Clear any error */
  1158.           if( fseek( in, where, SEEK_SET ) )
  1159.           {
  1160.             printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
  1161.                     buffer.file.name, current->node );
  1162.             fputs( "could not seek ready for next directory entry\n",
  1163.                stderr );
  1164.             if( out ) fclose( out );
  1165.             free( dirHash );
  1166.             current->next = *fileTree;
  1167.             fileTree = ¤t;
  1168.             return xrecover_chunkReadFail;
  1169.           }
  1170.         }
  1171.           }
  1172.         } /* End of having a chunk */
  1173.       }
  1174.     }
  1175.     else
  1176.     {
  1177.       printChunkDir( stderr, chunkNum, pathToHere );
  1178.       fprintf( stderr, "no chunk found for '%s'\n", buffer.file.name );
  1179.     }
  1180.  
  1181.     /* Add this filename to the head of the list */
  1182.     current->next = *fileTree;
  1183.     *fileTree = current;
  1184.       }
  1185.     }
  1186.   }
  1187.  
  1188.   if( where != -1L && start != -1L && thisChunk
  1189.       && ( ( maxSize = (unsigned int) ( where - start ) ) > thisChunk->size ) )
  1190.   {
  1191.     printChunkDir( stderr, chunkNum, pathToHere );
  1192.     fprintf( stderr, "chunkTable reports size as %d, appears to be %d\n",
  1193.          thisChunk->size, maxSize );
  1194.   }
  1195.   if( out ) fclose( out );
  1196.   free( dirHash );
  1197.   return xrecover_chunkSuccess;
  1198. }
  1199.  
  1200. static xrecover_chunkResult
  1201. xrecover_recoverArbitaryArea( FILE *in, const char *victim,
  1202.                   const unsigned long offset,
  1203.                   unsigned long size, const int verbose )
  1204. {
  1205.   xrecover_chunkResult status = xrecover_chunkSuccess;
  1206.   FILE *out;
  1207.  
  1208.   rewind( in );    /* Seems to need this to reset stream after seek beyond end */
  1209.   if( fseek( in, offset, SEEK_SET ) )
  1210.   {
  1211.     fprintf( stderr, "Could not seek to offset %ld\n", offset );
  1212.     return xrecover_chunkReadFail;
  1213.   }
  1214.  
  1215.   remove( victim );    /* This will ensure the correct file type (Data)
  1216.              * and that we can open R/ files */
  1217.  
  1218.   if( 0 == ( out  = fopen( victim, "wb" ) ) )
  1219.   {
  1220.     fprintf( stderr, "Offset %ld could not open file '%s' for output\n",
  1221.          offset, victim ? victim : "<NULL POINTER>" );
  1222.     return xrecover_chunkCantOpen;
  1223.   }
  1224.  
  1225.  
  1226.   while( size )
  1227.   {
  1228.     char buffer[xrecover_bufferSize];
  1229.     unsigned bytesWritten;
  1230.     unsigned bytesToRead = (unsigned) _min( size, sizeof( buffer ) );
  1231.     unsigned bytesRead;
  1232.  
  1233.     memset( buffer, 0, bytesToRead );
  1234.  
  1235.     bytesRead = fread( buffer, 1, bytesToRead, in );
  1236.     if( bytesRead != bytesToRead )
  1237.     {
  1238.       if( status == xrecover_chunkSuccess )
  1239.       {
  1240.     status = xrecover_chunkReadFail;
  1241.       }
  1242.       fprintf( stderr, "Offset %ld could not read %u bytes (got %u)\n",
  1243.            offset, bytesToRead, bytesRead );
  1244.       if( bytesRead == 0 )
  1245.       {
  1246.     if( verbose > 0 ) puts( "Bailing out" );
  1247.     break;
  1248.       }
  1249.       bytesRead = bytesToRead;    /* Pretend that we got them */
  1250.     }
  1251.  
  1252.     {
  1253.       bytesWritten = fwrite( buffer, 1, bytesRead, out );
  1254.       if( bytesRead != bytesWritten )
  1255.       {
  1256.     fprintf( stderr, "Offset %ld, file '%s' - only wrote %u byte(s) out of "
  1257.          "%d\n", offset, victim, bytesWritten, bytesRead );
  1258.     if( status == xrecover_chunkSuccess )
  1259.     {
  1260.       status = xrecover_chunkWriteFail;
  1261.     }
  1262.       }
  1263.     size -= bytesRead;
  1264.     }
  1265.   }
  1266.  
  1267.   fclose( out );
  1268.   return status;
  1269. }
  1270.  
  1271. static xrecover_chunkResult
  1272. xrecover_recoverChunkChkdsk( FILE *in, const char *victim, unsigned chunkNum,
  1273.                  const xFiles_chunk *chunk, unsigned forceSize,
  1274.                  BOOL dirsAsText, BOOL suppressFiles, int verbose )
  1275. /* Leave forceSize as 0 to believe the chunkTable entry
  1276.  * Otherwise, you take your life into your own hands!
  1277.  */
  1278. {
  1279.   FILE *out = 0;
  1280.   char buffer[xrecover_bufferSize];
  1281.   unsigned bytesToCopy = forceSize ? forceSize : chunk->size;
  1282.   unsigned bytesRead, bytesWritten;
  1283.   unsigned bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
  1284.   BOOL isDir = FALSE;
  1285.   xrecover_chunkResult status = xrecover_chunkSuccess;    /* optimism */
  1286.  
  1287.   rewind( in );    /* Seems to need this to reset stream after seek beyond end */
  1288.   if( fseek( in, chunk->offset, SEEK_SET ) )
  1289.   {
  1290.     fprintf( stderr, "Chunk %d could not seek to start (offset %d)\n", chunkNum,
  1291.          chunk->offset );
  1292.     return xrecover_chunkReadFail;
  1293.   }
  1294.  
  1295.   /* printf( "off=%d f=%d c=%d using %d\n", chunk->offset, forceSize, chunk->size, bytesToCopy ); */
  1296.   if( verbose > 3 )
  1297.   {
  1298.     printf( "Sought %d, got to %ld\n", chunk->offset, ftell( in ) );
  1299.   }
  1300.  
  1301.   memset( buffer, 0, sizeof( buffer ) );
  1302.  
  1303.   bytesRead = fread( buffer, 1, bytesToRead, in );
  1304.   if( bytesRead < bytesToRead )
  1305.   {
  1306.     fprintf( stderr, "Chunk %d could not read %u bytes (got %u)\n",
  1307.          chunkNum, sizeof( buffer ), bytesRead );
  1308.     return xrecover_chunkReadFail;
  1309.   }
  1310.  
  1311.   if( *(int *) buffer == xFiles_DIRSIG && dirsAsText )
  1312.   {
  1313.     /* Smells like a directory. */
  1314.     isDir = TRUE;
  1315.   }
  1316.   else if( suppressFiles ) return xrecover_chunkSuccess;
  1317.  
  1318.   if( victim )
  1319.   {
  1320.     remove( victim );    /* This will ensure the correct file type (Text or Data)
  1321.              * and that we can open R/ files */
  1322.     out = fopen( victim, isDir ? "w": "wb" );
  1323.   }
  1324.  
  1325.   if( ( victim != 0 ) && ( out == 0 ) )
  1326.   {
  1327.     status = xrecover_chunkCantOpen;
  1328.     fprintf( stderr, "Chunk %d could not open file '%s' for output\n", chunkNum,
  1329.          victim );
  1330.   }
  1331.  
  1332.   if( isDir )
  1333.   {
  1334.     /* It did use to pass in the header, with code to read the header in
  1335.      * xrecover_recoverDir() if a NULL pointer was passed, but I figured that
  1336.      * the amount of code generated was probably greater than the time saved for
  1337.      * a buffered filing system re-reading 16 bytes! */
  1338.     return xrecover_recoverDir( in, out, 0, 0, chunk->offset, chunkNum, chunk,
  1339.                 0, 0, 0, 0, verbose );
  1340.   }
  1341.  
  1342.   if( bytesRead > bytesToCopy ) bytesRead = bytesToCopy;
  1343.   /* Cope with short files */
  1344.  
  1345.   while( bytesToCopy )
  1346.   {
  1347.     /* I was going to write a stream based dir converter, but then I thought:
  1348.      * Sod it, let's just malloc a buffer big enough.
  1349.     if( isDir )
  1350.     {
  1351.  
  1352.     }
  1353.     else
  1354.      */
  1355.     {
  1356.       if( out )
  1357.       {
  1358.     bytesWritten = fwrite( buffer, 1, bytesRead, out );
  1359.     if( bytesRead != bytesWritten )
  1360.     {
  1361.       fprintf( stderr, "Chunk %d, file '%s' - only wrote %u byte(s) out of "
  1362.            "%d\n", chunkNum, victim, bytesWritten, bytesRead );
  1363.       if( status == xrecover_chunkSuccess )
  1364.       {
  1365.         status = xrecover_chunkWriteFail;
  1366.       }
  1367.     }
  1368.       }
  1369.     }
  1370.     bytesToCopy -= bytesRead;
  1371.     if( bytesToCopy != 0 )
  1372.     {
  1373.       bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
  1374.  
  1375.  
  1376.       memset( buffer, 0, bytesToRead );
  1377.       bytesRead = fread( buffer, 1, bytesToRead, in );
  1378.       if( bytesRead != bytesToRead )
  1379.       {
  1380.     fprintf( stderr, "Chunk %d could not read %u bytes (got %u)\n",
  1381.          chunkNum, bytesToRead, bytesRead );
  1382.     if( ferror( in ) )
  1383.     {
  1384.       perror( "Error on input x-file" );
  1385.     }
  1386.     else
  1387.     {
  1388.       fprintf( stderr, "Reached end of input x-file - now at %ld\n",
  1389.            ftell( in ) );
  1390.     }
  1391.     if( status == xrecover_chunkSuccess )
  1392.     {
  1393.       status = xrecover_chunkReadFail;
  1394.     }
  1395.     if( bytesRead == 0 )
  1396.     {
  1397.       if( verbose > 0 ) puts( "Bailing out" );
  1398.       break;
  1399.     }
  1400.     bytesRead = bytesToRead;    /* Pretend that we got them */
  1401.       }
  1402.     }
  1403.  
  1404.   }
  1405.   if( out ) fclose( out );
  1406.   return status;
  1407. }
  1408.  
  1409.  
  1410. static unsigned
  1411. xrecover_doGoodInList( FILE *in, const char *victim,
  1412.                const xFiles_chunk *chunkTable,
  1413.                xrecover_chunkTableCrossCheck *crossCheckTable,
  1414.                xrecover_filenameListEntry *fileTree,
  1415.                const BOOL useAlloc, const int verbose )
  1416. {
  1417.   unsigned problemFiles = 0;
  1418.   xrecover_chunkResult result;
  1419.   size_t prefixNameLen = strlen( victim ) + strlen( xrecover_dirSep ) + 1;
  1420.  
  1421.   while( fileTree )
  1422.   {
  1423.     if( fileTree->status & xrecover_foundChunk )
  1424.     {
  1425.       if( victim && 0 == ( fileTree->status & xrecover_started ) )
  1426.       {
  1427.     char *outPathname;
  1428.     const xFiles_chunk *thisChunk = chunkTable + fileTree->node;
  1429.     unsigned getSize = 0;    /* Believe the chunk by default */
  1430.     const char *filename = ( (char *) &(fileTree->dirEntry) )
  1431.                    + sizeof( xFiles_dirEntry );
  1432.  
  1433.     if( !( fileTree->status & xrecover_sizeMatches )
  1434.         && !( fileTree->dirEntry.attr & xFiles_isDir ) )
  1435.     /* Already flagged problem directories
  1436.      * directories report size in parent as 0, but have non-zero size in the
  1437.      * chunktable, hence they will always error here */
  1438.     {
  1439.       /* Which has it bigger - the directory or the chunk? */
  1440.       getSize = _max( fileTree->dirEntry.size, thisChunk->size );
  1441.  
  1442.       fprintf( stderr, "File '%s' chunk.size=%d, dirEntry.size=%d "
  1443.            "chunk.allocSize=%d\n"
  1444.            "Will try to recover %d%s\n", filename, thisChunk->size,
  1445.            fileTree->dirEntry.size, thisChunk->allocSize,
  1446.            getSize, ( getSize > thisChunk->allocSize )
  1447.                   ? " [which is greater than the allocated size]"
  1448.                   : "" );
  1449.     }
  1450.  
  1451.     if( useAlloc ) getSize = _max( getSize, thisChunk->allocSize );
  1452.  
  1453.     outPathname = malloc( prefixNameLen + strlen( filename ) );
  1454.  
  1455.     if( outPathname == 0 )
  1456.     {
  1457.       fprintf( stderr, "Could not get buffer for file '%s' - will add chunk"
  1458.            " %d to unclaimed list\n", filename, fileTree->node );
  1459.       result = xrecover_chunkCantOpen;
  1460.     }
  1461.     else
  1462.     {
  1463.       sprintf( outPathname, "%s%s%s", victim, xrecover_dirSep, filename );
  1464.       /* Go for it */
  1465.       result = ( fileTree->dirEntry.attr & xFiles_isDir )
  1466.            ? xrecover_chunkSuccess    /* Directory is already done */
  1467.            : xrecover_recoverChunkChkdsk( in, outPathname,
  1468.                           fileTree->node, thisChunk,
  1469.                           getSize, FALSE, FALSE,
  1470.                           verbose );
  1471.     }
  1472.  
  1473.     switch( result )
  1474.     {
  1475.       case xrecover_chunkSuccess:
  1476.       case xrecover_chunkWriteFail:
  1477.       /* Not my problem if your media won't write */
  1478.       {
  1479.         fileTree->status |= xrecover_readWhole;
  1480.  
  1481. #ifdef __riscos
  1482.         if( 0 == ( fileTree->status & xrecover_openFail ) )
  1483.         {
  1484.           _kernel_swi_regs regs;
  1485.           _kernel_oserror *err;
  1486.           regs.r[0] = 1;
  1487.           regs.r[1] = (int) outPathname;
  1488.           regs.r[2] = fileTree->dirEntry.load;
  1489.           regs.r[3] = fileTree->dirEntry.exec;
  1490.           regs.r[5] = fileTree->dirEntry.attr & 0xff;
  1491.  
  1492.           err = _kernel_swi( OS_File, ®s, ®s );
  1493.           if( err )
  1494.           {
  1495.         fprintf( stderr, "%s\n", err->errmess );
  1496.           }
  1497.         }
  1498. #endif
  1499.       }
  1500.       break;
  1501.  
  1502.       case xrecover_chunkCantOpen:
  1503.       {
  1504.         /* This chunk becomes one of the list of unrecovered chunks */
  1505.         fileTree->status |= xrecover_openFail;
  1506.         /* Make sure that we don't try to re-link this file with its chunk
  1507.          */
  1508.         if( 0 == --(crossCheckTable[fileTree->node].references) )
  1509.         {
  1510.           crossCheckTable[fileTree->node].file = NULL;
  1511.         }
  1512.         /* OK. Not beautiful - ideally each chunk should have a linked list
  1513.          * (or variable array - time for genArray.c to meet the outside
  1514.          * world?) of files that (cross)link to it, but ideally there should
  1515.          * be no cross links.
  1516.          *
  1517.          * If you'd like a copy of genArray.c, email <bagpuss@done.net>
  1518.          */
  1519.  
  1520.  
  1521.         /* ++problemFiles; */
  1522.         /* Don't need this, as *this file* wasn't the problem, rather it was
  1523.          * the output media
  1524.          */
  1525.         break;
  1526.       }
  1527.     free( outPathname );
  1528.     }
  1529.       }
  1530.       /* Else there was no chunk found for this file, or we have already made an
  1531.        * attempt to open it for output, and it has failed.
  1532.        */
  1533.       fileTree->status |= xrecover_started;
  1534.       /* Either we started it this run, or it was already started, so this makes
  1535.        * no difference
  1536.        */
  1537.     }
  1538.  
  1539.     else ++problemFiles;
  1540.     fileTree = fileTree->next;
  1541.   }
  1542.  
  1543.   return problemFiles;
  1544. }
  1545.  
  1546. static int xrecover_RecoverTree( FILE *in, const char *victim,
  1547.                  const char *outNameBuffer,
  1548.                  char *victimGoesHere,
  1549.                  unsigned chunkCount,
  1550.                  const xFiles_header *header,
  1551.                  const xFiles_chunk *chunkTable,
  1552.                  const unsigned long *rootDirOffset,
  1553.                  const unsigned maxChunk, BOOL dirsAsText,
  1554.                  BOOL suppressFiles, const BOOL useAlloc,
  1555.                  const int verbose )
  1556. {
  1557.   unsigned rootDirChunk = header ? header->rootChunk : 0;
  1558.   unsigned long useRootDirOffset;
  1559.   xrecover_chunkTableCrossCheck *crossCheckTable;
  1560.   xrecover_filenameListEntry *fileTree = 0;
  1561.   unsigned loop;
  1562.   unsigned problemFiles, unclaimedChunks = 0;
  1563.  
  1564.   if( rootDirOffset == 0 )
  1565.   {
  1566.     if( rootDirChunk == 0 || chunkTable == 0 )
  1567.     {
  1568.       fputs( "Can't find root directory.\n", stderr );
  1569.       return 1;
  1570.     }
  1571.     useRootDirOffset = ( chunkTable + rootDirChunk )->offset;
  1572.   }
  1573.   else
  1574.   {
  1575.     useRootDirOffset = *rootDirOffset;
  1576.     rootDirChunk = xrecover_chunkFromOffset( chunkTable, maxChunk,
  1577.                          useRootDirOffset );
  1578.     if( verbose > 1 )
  1579.     {
  1580.       printf( "Root dir offset %ld ", useRootDirOffset );
  1581.       if( rootDirChunk )
  1582.       {
  1583.     printf( "corresponds to chunk %d\n", rootDirChunk );
  1584.       }
  1585.       else
  1586.       {
  1587.     puts( "does not correspond to any used chunk" );
  1588.       }
  1589.     }
  1590.   }
  1591.  
  1592.   if( fseek( in, useRootDirOffset, SEEK_SET ) )
  1593.   {
  1594.     fprintf( stderr, "Could not seek to root directory at %ld\n",
  1595.          useRootDirOffset );
  1596.     return 1;
  1597.   }
  1598.   else if( verbose > 1 )
  1599.   {
  1600.     printf( "Using root directory at %ld\n", useRootDirOffset );
  1601.   }
  1602.  
  1603.   if( 0 == ( crossCheckTable = malloc( sizeof( xrecover_chunkTableCrossCheck )
  1604.                        * maxChunk ) ) )
  1605.   {
  1606.     fprintf( stderr, "Failed to get %u bytes of memory for crossCheckTable\n",
  1607.          sizeof( xrecover_chunkTableCrossCheck ) * maxChunk );
  1608.     return 1;
  1609.   }
  1610.  
  1611.   for( loop = maxChunk; loop-- != 0; )
  1612.   {
  1613.     crossCheckTable[loop].file = NULL;
  1614.     crossCheckTable[loop].status = xrecover_untouched;
  1615.  
  1616.   }
  1617.  
  1618.   xrecover_recoverDir( in, 0, victim, "", useRootDirOffset, rootDirChunk, 0,
  1619.                chunkTable, crossCheckTable, &fileTree, maxChunk,
  1620.                verbose );
  1621.  
  1622.   /*
  1623.   xrecover_printList( stdout, chunkTable, crossCheckTable, fileTree, verbose );       */
  1624.  
  1625.   problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
  1626.                     chunkTable, crossCheckTable,
  1627.                     fileTree, useAlloc, verbose );
  1628.  
  1629.  
  1630.   for( loop = maxChunk; --loop > 0; )
  1631.   /* 1 rather than 0 so that we miss the chunkTable chunk */
  1632.   {
  1633.     if( chunkTable[loop].usage != FREE && loop != rootDirChunk )
  1634.     {
  1635.       if( crossCheckTable[loop].file == NULL )
  1636.       {
  1637.     unclaimedChunks++;
  1638.       }
  1639.       else
  1640.       {
  1641.     test( crossCheckTable[loop].references == 1,
  1642.           "Chunk %d has %d references\n", loop,
  1643.           crossCheckTable[loop].references );
  1644.       }
  1645.     }
  1646.   }
  1647.  
  1648.   if( verbose > 0 )
  1649.   {
  1650.     if( problemFiles == 1 )
  1651.     {
  1652.       fputs( "There is one problem file", stdout );
  1653.     }
  1654.     else
  1655.     {
  1656.       printf( "There are %d problem files", problemFiles );
  1657.     }
  1658.     printf( " and %d unclaimed chunk%s\n", unclaimedChunks,
  1659.         ( unclaimedChunks == 1 ) ? "" : "s" );
  1660.   }
  1661.  
  1662.  
  1663.   if( problemFiles || unclaimedChunks )
  1664.   {
  1665.     /* Here goes. Let's try to match them up */
  1666.     xrecover_filenameListEntry **problemFile;
  1667.     const xFiles_chunk **unclaimedChunk, **currentChunk;
  1668.  
  1669.     problemFile = malloc( sizeof( xrecover_filenameListEntry *)
  1670.               * problemFiles );
  1671.     unclaimedChunk = malloc( sizeof( xFiles_chunk * ) * unclaimedChunks );
  1672.  
  1673.     if( problemFile == 0 && unclaimedChunk == 0 )
  1674.     {
  1675.       fputs( "Could not get memory to sort problem files and unclaimed "
  1676.          "chunks\n", stderr );
  1677.     }
  1678.     else
  1679.     {
  1680.       unsigned int lastSize = UINT_MAX;
  1681.       currentChunk = unclaimedChunk;
  1682.       /* Entry can never (reasonably) be this big as there will be some bytes
  1683.        * used for the (header/chunkTable/dir) */
  1684.       for( loop = maxChunk; --loop > 0 ; )
  1685.       {
  1686.     if( chunkTable[loop].usage != FREE && loop != rootDirChunk
  1687.         && crossCheckTable[loop].file == NULL )
  1688.     {
  1689.       *currentChunk++ = &(chunkTable[loop]);
  1690.     }
  1691.       }
  1692.  
  1693.       qsort( (void *) unclaimedChunk, unclaimedChunks, sizeof( xFiles_chunk * ),
  1694.          chunkSizeCmp );
  1695.  
  1696.       xrecover_getProblemList( fileTree, problemFile, problemFiles );
  1697.  
  1698.       qsort( problemFile, problemFiles, sizeof( xrecover_filenameListEntry * ),
  1699.          filenameListEntryCmp );
  1700.  
  1701.       test( currentChunk == unclaimedChunk + unclaimedChunks,
  1702.         "Awooga - can't count - first time there were %d unclaimed chunks, "
  1703.         "now there are %d\n", unclaimedChunks,
  1704.         currentChunk - unclaimedChunk );
  1705.  
  1706.       for( loop = problemFiles; loop-- > 0 ; )
  1707.       {
  1708.     const char *thisFile = ( (char *) &(problemFile[loop]->dirEntry) )
  1709.                    + sizeof( xFiles_dirEntry );
  1710.     const unsigned thisSize = problemFile[loop]->dirEntry.size;
  1711.     unsigned index;
  1712.  
  1713.     /* If this file has the same size as the previous file ignore it */
  1714.     if( thisSize == lastSize ) continue;
  1715.     /* If this file has the same size as the next file,
  1716.        0: Make sure that the next file is ignored too
  1717.        1: Ignore it
  1718.      */
  1719.     if( loop > 0 && ( problemFile[loop-1]->dirEntry.size == thisSize ) )
  1720.     {
  1721.       lastSize = thisSize;
  1722.       if( verbose > 0 )
  1723.       {
  1724.         printf( "As files '%s' and '%s' have same size (%d) "
  1725.             "cannot attempt to match to unclaimed chunks\n", thisFile,
  1726.             ( (char *) &(problemFile[loop - 1]->dirEntry) )
  1727.               + sizeof( xFiles_dirEntry ), thisSize );
  1728.       }
  1729.       continue;
  1730.     }
  1731.  
  1732.  
  1733.     while( ( currentChunk-- > unclaimedChunk )
  1734.            && chunkTable[ index = *currentChunk - chunkTable ].size
  1735.           > thisSize );
  1736.     /* 8-). index = *currentChunk - chunkTable; somewhere in the middle of
  1737.      * all that. Anyway, we get here when a chunk size matches or we run out
  1738.      * of chunks
  1739.      */
  1740.  
  1741.     if( currentChunk < unclaimedChunk    /* Out of chunks */
  1742.         || chunkTable[index].size < thisSize )
  1743.     /* index won't be used here before it is set */
  1744.     {
  1745.       if( verbose > 0 )
  1746.       {
  1747.         printf( "Cannot find chunk to match file '%s' size %d\n", thisFile,
  1748.             thisSize );
  1749.       }
  1750.     }
  1751.     else if( currentChunk > unclaimedChunk    /* At least one more chunk */
  1752.          && chunkTable[ *( currentChunk - 1 ) - chunkTable ].size
  1753.             == thisSize )
  1754.     {
  1755.       if( verbose > 0 )
  1756.       {
  1757.         printf( "More than one chunk to match file '%s' size %d\n",
  1758.             thisFile, thisSize );
  1759.       }
  1760.       /* Skip past chunks of this size */
  1761.       while( ( currentChunk-- > unclaimedChunk )
  1762.            && chunkTable[ *currentChunk - chunkTable ].size == thisSize );
  1763.     }
  1764.     else
  1765.     {
  1766.       /* ¡!¡Hit!¡!
  1767.        * Exactly one file of this size matched exactly one chunk.
  1768.        * Let's brag about it on stderr
  1769.        */
  1770.       fprintf( stderr, "Chunk %d size %d matches file '%s' size %d\n"
  1771.            "Will attempt recovery of this chunk as this file\n",
  1772.            index, chunkTable[index].size, thisFile, thisSize );
  1773.       /* Reset the flags so that a new attempt on recovery will be made */
  1774.       problemFile[loop]->status |= xrecover_foundChunk
  1775.                        | xrecover_sizeMatches;
  1776.       problemFile[loop]->status &= ~xrecover_started;
  1777.       problemFile[loop]->node = index;
  1778.       crossCheckTable[index].references = 1;
  1779.       crossCheckTable[index].file = problemFile[loop];
  1780.     }
  1781.       }
  1782.  
  1783.  
  1784.  
  1785.     }
  1786.  
  1787.     free( problemFile );
  1788.     free( (void *) unclaimedChunk );
  1789.  
  1790.     /* Let's have another go */
  1791.     problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
  1792.                       chunkTable, crossCheckTable,
  1793.                       fileTree, useAlloc, verbose );
  1794.   }
  1795.  
  1796.   xrecover_freeList( &fileTree );
  1797.   /* Don't need this anymore. Now go for the unclaimed chunks */
  1798.  
  1799.   if( victim )
  1800.   {
  1801.     for( loop = maxChunk; --loop > 0 ; )
  1802.     {
  1803.       if( chunkTable[loop].usage == FREE || loop == rootDirChunk
  1804.       || crossCheckTable[loop].file != 0 )
  1805.     continue;
  1806.  
  1807.       /* Unreferenced, non-free chunk. Looks ripe for recovery */
  1808.       if( victim )
  1809.       {
  1810.     sprintf( victimGoesHere, chkdskString, chunkCount++ );
  1811.       }
  1812.       /* outNameBuffer == 0 iff victim == 0 */
  1813.       xrecover_recoverChunkChkdsk( in, outNameBuffer, loop,
  1814.                    chunkTable + loop,
  1815.                    useAlloc ? chunkTable[loop].allocSize
  1816.                         : 0,
  1817.                    dirsAsText, suppressFiles, verbose );
  1818.     }
  1819.   }
  1820.  
  1821.   free( crossCheckTable );
  1822.   return 0;
  1823. }
  1824.  
  1825. static int xrecover_RecoverFile( const char *problem, const char *victim,
  1826.                  const unsigned long *chunkTableOffset,
  1827.                  const unsigned long *rootDirOffset,
  1828.                  const xrecover_qualityLevel qualityOfJob,
  1829.                  const BOOL dirsAsText,
  1830.                  const BOOL canCreateVictim,
  1831.                  const BOOL suppressFiles, const BOOL writeFree,
  1832.                  const BOOL useAlloc, const int verbose )
  1833. {
  1834.   /* Victim == 0 to suppress all writes */
  1835.   FILE *in;
  1836.   long problemSize;
  1837.   size_t victimPathLen = victim ? strlen( victim ) + strlen( xrecover_dirSep )
  1838.                 : 0;
  1839.   size_t outNameBufferLen = victim ? ( victimPathLen
  1840.                        + strlen( chkdskString ) + 1  )
  1841.                    : 0;
  1842.   char *outNameBuffer = victim ? malloc( outNameBufferLen ) : 0;
  1843.   unsigned maxChunk;
  1844.   unsigned readChunks;
  1845.   unsigned chunkLoop;
  1846.   unsigned chunkCount = 0;
  1847.   unsigned long seekTo;
  1848.   xFiles_header header;
  1849.   xFiles_chunk *chunkTable;
  1850.   xFiles_chunk rootChunk;
  1851.   const xFiles_chunk **sortedChunkTable, **currentChunk, **endSorted;
  1852.   unsigned long endLastChunk = 0;
  1853. #ifdef __riscos
  1854.   _kernel_swi_regs regs;
  1855.   _kernel_oserror *err;
  1856.   int type;
  1857. #endif
  1858.  
  1859.  
  1860. #ifdef __riscos
  1861.   if( victim )
  1862.   {
  1863.     regs.r[0] = 5;
  1864.     regs.r[1] = (int) victim;
  1865.  
  1866.  
  1867.     if( err = _kernel_swi( OS_File, ®s, ®s ), err )
  1868.     {
  1869.       fprintf( stderr, "%s\n", err->errmess );
  1870.       return 1;
  1871.     }
  1872.  
  1873.     type = regs.r[0];
  1874.  
  1875.     if( type == 0 && canCreateVictim )
  1876.     {
  1877.       if( verbose > 0 ) printf( "Creating x-file '%s'\n", victim );
  1878.       regs.r[0] = 11;
  1879.       regs.r[2] = xFiles_TYPE;
  1880.       regs.r[4] = 0;
  1881.       regs.r[5] = 0;
  1882.       if( err = _kernel_swi( OS_File, ®s, ®s ), err )
  1883.       {
  1884.     fprintf( stderr, "%s\n", err->errmess );
  1885.     return 1;
  1886.       }
  1887.     }
  1888.     else if( 0 == ( type & 2 ) )
  1889.     {
  1890.       /* Use OS File to make a message */
  1891.       regs.r[0] = 19;
  1892.       regs.r[2] = type;
  1893.       err = _kernel_swi( OS_File, ®s, ®s );
  1894.       if( !err ) err = (_kernel_oserror *) regs.r[0];
  1895.       /* Get the generated error message if no erroneous error occurred  */
  1896.       fprintf( stderr, "%s\n", err->errmess );
  1897.       return 1;
  1898.     }
  1899.   }
  1900. #endif    /* end of Risc OS specific bits */
  1901.  
  1902.   /* hopefully from now on is ANSI */
  1903.  
  1904.   if( victim )
  1905.   {
  1906.     if( 0 == outNameBuffer )
  1907.     {
  1908.       fprintf( stderr, "Failed to get %u bytes of memory for outNameBuffer\n",
  1909.          outNameBufferLen );
  1910.       return 1;
  1911.     }
  1912.     sprintf( outNameBuffer, "%s%s", victim, xrecover_dirSep );
  1913.   }
  1914.  
  1915.   if( 0 == ( in = fopen( problem, "rb" ) ) )
  1916.   {
  1917.     fprintf( stderr, "Could not open file '%s'\n", problem );
  1918.     free( outNameBuffer );
  1919.     return 1;
  1920.   }
  1921.  
  1922.   if( fseek( in, 0, SEEK_END ) || ( -1L == ( problemSize = ftell( in ) ) ) )
  1923.   {
  1924.     fprintf( stderr, "Could not find size of file '%s'\n", problem );
  1925.     free( outNameBuffer );
  1926.     return 1;
  1927.   }
  1928.  
  1929.   rewind( in );
  1930.  
  1931.   if( 0 == fread( &header, sizeof( header ), 1, in ) )
  1932.   {
  1933.     fprintf( stderr, "Could not read header from file '%s'\n", problem );
  1934.     free( outNameBuffer );
  1935.     return 1;
  1936.   }
  1937.  
  1938.   test( header.sig == xFiles_SIG,
  1939.     "Missing xFiles_SIG - read %8X, should be %8X\n",
  1940.     header.sig, xFiles_SIG );
  1941.   if( verbose > 1 )
  1942.   {
  1943.     test( header.hdrSize == sizeof( xFiles_header ),
  1944.       "Illegal hdrSize %d\n",
  1945.       header.hdrSize );
  1946.  
  1947.     test( header.structureVersion == xFiles_STRUCTUREVERSION,
  1948.       "Illegal structureVersion %d\n",
  1949.         header.structureVersion );
  1950.  
  1951.     test( header.directoryVersion == xFiles_DIRECTORYVERSION,
  1952.     "Illegal directoryVersion %d\n",
  1953.       header.directoryVersion );
  1954.  
  1955.   /* testChunk(&info, 0, &  header.chunkTable ); */
  1956.  
  1957.   test( power2( header.allocationUnit ),
  1958.     "Illegal allocationUnit %d\n", header.allocationUnit );
  1959.   }
  1960.  
  1961.   /*
  1962.   test( ( roundUp( &header, sizeof( header ) ) == header.chunkTable.offset ),
  1963.     "chunkTable not found 1 allocationUnit into file\n"
  1964.     "Expected %d\n"
  1965.     "Actually %d\n", roundUp( &header, sizeof( header ) ),
  1966.     header.chunkTable.offset ); */
  1967.  
  1968.   seekTo = chunkTableOffset ? *chunkTableOffset : header.chunkTable.offset;
  1969.   if( fseek( in, seekTo, SEEK_SET ) )
  1970.   {
  1971.     fprintf( stderr, "Could not seek to chunkTable at %ld\n", seekTo );
  1972.     free( outNameBuffer );
  1973.     return 1;
  1974.   }
  1975.  
  1976.    ;
  1977.  
  1978.   if( 0 == fread( &rootChunk, sizeof( xFiles_chunk ), 1, in ) )
  1979.   {
  1980.     fprintf( stderr,
  1981.          "Could not read first chunk of chunkTable from file '%s'\n",
  1982.          problem );
  1983.     free( outNameBuffer );
  1984.     return 1;
  1985.   }
  1986.  
  1987.   /* Derive size from header, unless chunkTable offset was specified */
  1988.   maxChunk = ( chunkTableOffset ? rootChunk.size
  1989.                   : header.chunkTable.size )
  1990.                    / sizeof( xFiles_chunk );
  1991.  
  1992.   test( 0 == ( header.chunkTable.size % sizeof( xFiles_chunk ) ),
  1993.     "chunkTable size %d (from header) is not a multiple of %d\n",
  1994.     header.chunkTable.size, sizeof( xFiles_chunk ) );
  1995.  
  1996.   test( 0 == ( rootChunk.size % sizeof( xFiles_chunk ) ),
  1997.     "chunkTable size %d (from chunkTable) is not a multiple of %d\n",
  1998.     rootChunk.size, sizeof( xFiles_chunk ) );
  1999.  
  2000.   if( 0 == ( chunkTable = malloc( sizeof( xFiles_chunk ) * maxChunk ) ) )
  2001.   {
  2002.     fprintf( stderr, "Failed to get %u bytes of memory for chunkTable\n",
  2003.          sizeof( xFiles_chunk ) * maxChunk );
  2004.     fclose( in );
  2005.     free( outNameBuffer );
  2006.     return 1;
  2007.   }
  2008.   if( 0 == ( sortedChunkTable = malloc( sizeof( xFiles_chunk * ) * maxChunk ) )    )
  2009.   {
  2010.     fprintf( stderr, "Failed to get %u bytes of memory to sort chunkTable\n",
  2011.          sizeof( xFiles_chunk * ) * maxChunk );
  2012.     free( chunkTable );
  2013.     free( outNameBuffer );
  2014.     fclose( in );
  2015.     return 1;
  2016.   }
  2017.  
  2018.   if( verbose > 0 )
  2019.   {
  2020.     printf( "'%s' has %u chunk(s)\n", problem, maxChunk );
  2021.   }
  2022.  
  2023.   *chunkTable = rootChunk;
  2024.   
  2025.   readChunks = fread( chunkTable + 1, sizeof( xFiles_chunk ), maxChunk - 1,
  2026.                 in );
  2027.  
  2028.   if( readChunks < maxChunk - 1 )
  2029.   {
  2030.     fprintf( stderr,
  2031.          "Only read %d chunks out of %d allegedly in chunkTable\n"
  2032.          "Will only deal with these %d chunkss\n",
  2033.          readChunks + 1, maxChunk, readChunks + 1);
  2034.     maxChunk = readChunks + 1;
  2035.   }
  2036.  
  2037.  
  2038.   for( chunkLoop = maxChunk ; chunkLoop-- > 0;
  2039.        sortedChunkTable[chunkLoop] = &(chunkTable[chunkLoop]) );
  2040.  
  2041.   qsort( (void *) sortedChunkTable, maxChunk, sizeof( xFiles_chunk * ),
  2042.      chunkOffsetCmp );
  2043.  
  2044.   if( verbose > 1 )
  2045.   {
  2046.     puts( "Chunk  Offset    Size   Usage Allocated" );
  2047.     /* Puts adds \n. Isn't the ANSI C library wonderfully consistent:
  2048.      * puts( a ) == fputs( a + '\n', stdout ) == fprintf( stdout, "%s\n", a ) */
  2049.   }
  2050.  
  2051.   /* OK. Go through these in offset order */
  2052.   for( currentChunk = sortedChunkTable, endSorted = sortedChunkTable + maxChunk;
  2053.        currentChunk < endSorted; currentChunk++ )
  2054.   {
  2055.     chunkLoop = *currentChunk - chunkTable;
  2056.  
  2057.     if( chunkTable[chunkLoop].usage == FREE )
  2058.     {
  2059.       if( verbose > 4 )
  2060.       {
  2061.     printf( " %4d %7d            FREE\n", chunkLoop,
  2062.         chunkTable[chunkLoop].offset );
  2063.       }
  2064.       test( chunkTable[chunkLoop].offset < maxChunk,
  2065.         "Chunk %d (free) bad offset %08x\n", chunkLoop,
  2066.         chunkTable[chunkLoop].offset );
  2067.  
  2068.       test( chunkTable[chunkLoop].size == FREE,
  2069.         "Chunk %d (free) size is %08x (should be %08x)\n",
  2070.         chunkLoop, chunkTable[chunkLoop].size, FREE );
  2071.  
  2072.       test( chunkTable[chunkLoop].allocSize == FREE,
  2073.         "Chunk %d (free) allocSize is %08x (should be %08x)\n",
  2074.         chunkLoop, chunkTable[chunkLoop].allocSize, FREE );
  2075.     }
  2076.     else
  2077.     {
  2078.       test( okOffset( &header, chunkTable[chunkLoop].offset ),
  2079.         "Chunk %d (used) bad offset %08x\n", chunkLoop,
  2080.         chunkTable[chunkLoop].offset );
  2081.  
  2082.       if( endLastChunk != -1L )
  2083.       {
  2084.     /* offset is unsigned - if it's 0 for start of file then offset can
  2085.        *never* be less than endLastChunk */
  2086.     if( chunkTable[chunkLoop].offset < endLastChunk )
  2087.     {
  2088.       fprintf( stderr, "Chunk %d offset %d overlaps end of previous chunk"
  2089.            " (%d, ends at %ld)\n", chunkLoop,
  2090.            chunkTable[chunkLoop].offset,
  2091.            *( currentChunk - 1 ) - chunkTable, endLastChunk );
  2092.     }
  2093.     else if( chunkTable[chunkLoop].offset > endLastChunk )
  2094.     {
  2095.       if( verbose > 1 )
  2096.       {
  2097.         printf( "%ld byte(s) %s between ",
  2098.         chunkTable[chunkLoop].offset - endLastChunk,
  2099.         endLastChunk ? "free space" : "x-file header" );
  2100.  
  2101.         if( endLastChunk )
  2102.         {
  2103.           printf( "chunk %d", *( currentChunk - 1 ) - chunkTable );
  2104.         }
  2105.         else
  2106.         {
  2107.           fputs( "start of file", stdout );    /* puts appends '\n' */
  2108.         }
  2109.  
  2110.         printf( " and chunk %d\n", chunkLoop );
  2111.  
  2112.       }
  2113.       if( writeFree && victim )
  2114.       {
  2115.         sprintf( outNameBuffer + victimPathLen, chkdskString,
  2116.              chunkCount++ );
  2117.         xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
  2118.                       chunkTable[chunkLoop].offset
  2119.                       - endLastChunk, verbose );
  2120.       }
  2121.     }
  2122.  
  2123.       }
  2124.  
  2125.       if( verbose > 1 )
  2126.       {
  2127.     printf( " %4d %7.d %7.d %7.d %9.d\n", chunkLoop,
  2128.         chunkTable[chunkLoop].offset, chunkTable[chunkLoop].size,
  2129.         chunkTable[chunkLoop].usage, chunkTable[chunkLoop].allocSize );
  2130.       }
  2131.       test( (chunkTable[chunkLoop].size <= chunkTable[chunkLoop].allocSize ),
  2132.         "Chunk %d (used) bad size %08x (allocSize %08x)\n",
  2133.         chunkLoop, chunkTable[chunkLoop].size,
  2134.         chunkTable[chunkLoop].allocSize );
  2135.       test( okOffset( &header, chunkTable[chunkLoop].allocSize ),
  2136.         "Chunk %d (used) bad allocSize %08x\n", chunkLoop,
  2137.         chunkTable[chunkLoop].allocSize );
  2138.       test( ( problemSize > chunkTable[chunkLoop].offset ),
  2139.         "Chunk %d (used) offset %08x > size of '%s' (%08x)\n", chunkLoop,
  2140.         chunkTable[chunkLoop].offset, problem, problemSize );
  2141.  
  2142.       endLastChunk = chunkTable[chunkLoop].offset
  2143.              + chunkTable[chunkLoop].allocSize;
  2144.  
  2145.       /* Whatever the method, recover the chunktable here if useAlloc is set */
  2146.       if( qualityOfJob == xrecover_qualityChkdsk
  2147.       || ( chunkLoop == 0 && useAlloc && !suppressFiles) )
  2148.       /* I alledge that this is not a special case. Well it is ½ a special
  2149.        * case. The useAlloc *is* but the !suppressFiles is cosmetic.
  2150.        * If it were not there, one gets file0000 as the header, and file0002
  2151.        * (if necessary) as the free space at the end of 'problem'
  2152.        */
  2153.       {
  2154.     if( victim )
  2155.     {
  2156.       sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
  2157.     }
  2158.     /* outNameBuffer == 0 iff victim == 0 */
  2159.     xrecover_recoverChunkChkdsk( in, outNameBuffer, chunkLoop,
  2160.                      chunkTable + chunkLoop,
  2161.                      useAlloc ? chunkTable[chunkLoop].allocSize
  2162.                           : 0,
  2163.                      dirsAsText, suppressFiles, verbose );
  2164.       }
  2165.     }
  2166.   }
  2167.  
  2168.   if( endLastChunk != -1L && problemSize > endLastChunk )
  2169.   {
  2170.     /* offset is unsigned - if it's 0 for start of file then offset can
  2171.        *never* be less than endLastChunk */
  2172.     if( verbose > 1 )
  2173.     {
  2174.       printf( "%ld byte(s) free space between chunk %d and end of file\n",
  2175.           problemSize - endLastChunk, *( currentChunk - 1 ) - chunkTable );
  2176.     }
  2177.  
  2178.     if( writeFree && victim )
  2179.     {
  2180.       sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
  2181.       xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
  2182.                     problemSize - endLastChunk, verbose );
  2183.     }
  2184.   }
  2185.  
  2186.   switch( qualityOfJob )
  2187.   {
  2188.     case xrecover_qualityChkdsk:
  2189.     break;    /* Done this while checking chunk table */
  2190.  
  2191.     case xrecover_qualityReadDirs:
  2192.     {
  2193.       xrecover_RecoverTree( in, victim, outNameBuffer,
  2194.                 outNameBuffer + victimPathLen, chunkCount,
  2195.                 &header, chunkTable, rootDirOffset,
  2196.                 maxChunk, dirsAsText, suppressFiles, useAlloc,
  2197.                 verbose );
  2198.     }
  2199.     break;
  2200.  
  2201.     default:
  2202.     fprintf( stderr, "Unimplemented quality %d\n", qualityOfJob );
  2203.   }
  2204.   free( sortedChunkTable );
  2205.   free( chunkTable );
  2206.   free( outNameBuffer );
  2207.   /* Some systems will memory leak if one doesn't do this */
  2208.   /* (Not freeing things is a Vaxism (and Unixism) ) */
  2209.   return 0;
  2210. }
  2211.  
  2212. int main( int argc, char *argv[] )
  2213. {
  2214.   int argn;
  2215.   int verbose = 0;
  2216.   const char *problem = 0;    /* aka x-file to fix */
  2217.   const char  *victim = 0;    /* aka destination 'directory' */
  2218.   BOOL dirsAsText = TRUE;
  2219.   BOOL suppressOutput = FALSE;
  2220.   BOOL canCreateVictim = FALSE;
  2221.   BOOL suppressFiles = FALSE;
  2222.   BOOL xguess = FALSE;
  2223.   BOOL useAlloc = FALSE;
  2224.   BOOL writeFree = FALSE;
  2225.   xrecover_Offset rootDir = { FALSE, 0 };
  2226.   xrecover_Offset chunkTable = { FALSE, 0 };
  2227.   xrecover_qualityLevel qualityOfJob = xrecover_qualityReadDirs;
  2228.  
  2229. #ifdef __riscos
  2230. #ifdef __GNUC__
  2231.   __uname_control |= __UNAME_NO_PROCESS;
  2232. #endif
  2233. #endif
  2234.   for( argn = 1; ( argn < argc ) && ( *argv[argn] == '-' ); argn++ )
  2235.   {
  2236.     const char *current = argv[argn] + 1;
  2237.     if( *current == '-' )
  2238.     {
  2239.       argn++;
  2240.       break;    /* Closet goto out of the for loop */
  2241.       /* -- marks end of options à la Unix */
  2242.     }
  2243.     while( current && *current )
  2244.     {
  2245.       switch( tolower( *current ) )
  2246.       {
  2247.     case 'a':
  2248.       useAlloc = TRUE;
  2249.       break;
  2250. #ifdef __riscos
  2251.     case 'c':
  2252.       canCreateVictim = TRUE;
  2253.       break;
  2254. #endif
  2255.     case 'd':
  2256.       dirsAsText = FALSE;
  2257.       break;
  2258.     case 'f':
  2259.       writeFree = TRUE;
  2260.       break;
  2261.     case 'g':
  2262.       xguess = TRUE;
  2263.     case 'n':
  2264.       suppressOutput = TRUE;
  2265.       break;
  2266.     case 's':
  2267.       suppressFiles = TRUE;
  2268.       break;
  2269.     case 'v':
  2270.       verbose += 1;
  2271.       break;
  2272.  
  2273.     case 'r':
  2274.       if( *(current + 1) )
  2275.       {
  2276.         if( FALSE == xrecover_ReadOffset( &rootDir, current + 1 ) )
  2277.         {
  2278.           argn = argc + 1;
  2279.         }
  2280.       }
  2281.       else
  2282.       {
  2283.         argn = xrecover_ReadOffset( &rootDir, argv[argn+1] ) ? argn + 1
  2284.                                  : argc + 1;
  2285.       }
  2286.       current = 0; current--;
  2287.       /* Whatever happens, skip on to next argv */
  2288.       break;
  2289.     /* If TRUE, value is successfully read, so skip over value
  2290.      * Else argn = argc will break out of this loop
  2291.      * AND trigger syntax message */
  2292.     case 't':
  2293.       if( *(current + 1) )
  2294.       {
  2295.         if( FALSE == xrecover_ReadOffset( &chunkTable, current + 1 ) )
  2296.         {
  2297.           argn = argc + 1;
  2298.         }
  2299.       }
  2300.       else
  2301.       {
  2302.         argn = xrecover_ReadOffset( &chunkTable, argv[argn+1] ) ? argn + 1
  2303.                                     : argc + 1;
  2304.       }
  2305.       current = 0; current--;
  2306.       /* Whatever happens, skip on to next argv */
  2307.       break;
  2308.  
  2309.     case '0':
  2310.       qualityOfJob= xrecover_qualityEvery1024;
  2311.       break;
  2312.     case '1':
  2313.       qualityOfJob= xrecover_qualityChkdsk;
  2314.       break;
  2315.     case '2':
  2316.       qualityOfJob= xrecover_qualityReadDirs;
  2317.       break;
  2318.  
  2319.     case 'm':
  2320.       if( *(current + 1) == '$' )
  2321.       {
  2322.         chkdskString = chkdskStringMS;
  2323.         current++;
  2324.         break;
  2325.       } /* Else fall through to this: */
  2326.     default:
  2327.       fprintf( stderr, "Ignoring unknown option '%c'\n", *current );
  2328.       break;
  2329.       }
  2330.       current++;
  2331.     }    /* while( current && *current ) */
  2332.   }
  2333.  
  2334.   if( argn < argc )
  2335.   {
  2336.     problem = argv[argn++];
  2337.   }
  2338.   if( argn < argc )
  2339.   {
  2340.     victim = argv[argn++];
  2341.   }
  2342.  
  2343.   if( !problem || ( ( victim == 0 ) && !suppressOutput ) || ( argn != argc ) )
  2344.   {
  2345.     /* When is Acorn's F***ing Run time going to do tabs properly? */
  2346.     /* Use gcc - free software always offers better value for money */
  2347.     fputs( "Syntax:\tx-recover [options] <x-file> <dest-dir>\n"
  2348.        "\t\t-v for verbose info (more v's for more verbosity!)\n"
  2349. #ifdef __riscos
  2350.        "\t\t-c to create dest-dir as an x-file if necessary\n"
  2351. #endif
  2352.        "\t\t-d To output directories in raw binary form\n"
  2353.        "\t\t-g To scan file to find the chunk table and root directory\n"
  2354.        "\t\t   (implies -n)\n"
  2355.        "\t\t-n To suppress all disc output (still report integrity checks)"
  2356.        "\n"
  2357.        "\t\t-r <offset> to specify the offset of the root directory\n"
  2358.        "\t\t-s To suppress all file output (but create directory tree)\n"
  2359.        "\t\t-t <offset> to specify the offset of the chunkTable\n"
  2360.        "\t\t-a To round up all file sizes to the size of the chunk\n"
  2361.        "\t\t-f To write out all free space between chunks as files\n"
  2362. /*       "\t\t-e To round up the x-file to the size allocated on disc\n" */
  2363. /* nasty filing system fills it with zeros, so not much point. I don't think
  2364.  * ADFS did this on Risc OS 2 (Network filing systems did, being security
  2365.  * concious */
  2366. /*       "\t\t-0 Attempt to split chunks ignoring chunk table\n" */
  2367.        "\t\t-1 Output all used chunks as files (needs intact chunk table)\n"
  2368.        "\t\t-2 Attempt to traverse the directory tree and recreate the \n"
  2369.        "\t\t   x-file (default method)\n",
  2370.        stderr );
  2371.     return 1;
  2372.   }
  2373.  
  2374.   if( xguess ) return xrecover_xguess( problem );
  2375.  
  2376.   if( verbose > 0 )
  2377.   {
  2378.     if( victim )
  2379.     {
  2380.       printf( "%s -> %s (v=%d)\n", problem, victim, verbose );
  2381.     }
  2382.     else
  2383.     {
  2384.       printf( "%s [suppress output] (v=%d)\n", problem, verbose );
  2385.     }
  2386.     if( rootDir.valid )
  2387.     {
  2388.       printf( "Read root directory from file offset %ld\n", rootDir.offset );
  2389.     }
  2390.     if( chunkTable.valid )
  2391.     {
  2392.       printf( "Read chunk table from file offset %ld\n", chunkTable.offset );
  2393.     }
  2394.   }
  2395.   return xrecover_RecoverFile( problem, suppressOutput ? 0 : victim,
  2396.                    chunkTable.valid ? &(chunkTable.offset) : 0,
  2397.                    rootDir.valid ? &(rootDir.offset) : 0,
  2398.                    qualityOfJob, dirsAsText,
  2399.                    canCreateVictim, suppressFiles, writeFree,
  2400.                    useAlloc, verbose );
  2401. }
  2402.  
  2403.  
  2404.